﻿/*
 * 项目简介:基于d3的图形库,通过输入数据及配置信息产生svg图像对数据进行可视化
 * 
 * 设计思路:定义全局变量(namespace)chart215用于图形库入口,
 * 		定义class  Chart215作为封装数据: 渲染图形使用的html元素,通过调用不同方法,传入不同数据产生不同图形
 * 		为Chart215类定义方法,调用不同方法针对容器产生不同图形
 * 
 * 图形库使用方法: chart215.select(selector).method(config);
 * 说明: chart215.select(selector)通过selector传入图形渲染所需容器的css选择器,
 * 		返回对应容器包装后的Chart215对象.
 * 		method(config)针对需要调用不同method.产生不同图形
 * 		如:chart215.select("#chart").parallelCoordinate()将针对id为chart的元素渲染平行坐标
 * 		config为传入信息的配置类
 * 
 * 
 * 
 * 
 * */


window.chart215 = (function () {
	function Chart215 (selection) {
		this.selection = d3.select(selection);
	};
	
	function extend (target, source)
	{
		for (var key in source)
		{
			target[key] = source[key];
		}
		return target;
	}
	/*
	 * 说明: 传入配置,产生平行坐标系,
	 * 
	 * data 格式: [ 
	 * 			{"cluster": "clusterName", "attr1": aaa, "attr2": bbb},
	 * 			{"cluster": "clusterName", "attr1": aaa, "attr2": bbb}
	 * ]
	 * 其中cluster为必选,同一个聚类数据项显示时颜色相同,attr1---attrN为数据项的n维数据,必须为数值型
	 */
	Chart215.prototype.parallelCoordinate = function (config) {
		  var __ = {
				    data: [],
				    dimensions: [], // 数据项属性名: attr1, attr2...
				    dimensionTitles: {},
				    types: {},
				    axis : d3.svg.axis().orient("left"),
				    brushed: false,
				    mode: "default",
				    foreground: null,
				    line: d3.svg.line(),
				    gAttributes: null,
				    dragIndex: 0,
				    x: d3.scale.ordinal(),
				    y: {},
				    color: d3.scale.category10(),
				    rate: 20,
				    width: 600,
				    height: 300,
				    margin: { top: 24, right: 20, bottom: 12, left: 20 },
				    composite: "source-over",
				    alpha: 0.7
				  };
		  extend(__, config);
		  
		  var container = this.selection.append("svg")
		  		.attr("width", __.width + __.margin.left + __.margin.right)
		  		.attr("height", __.height + __.margin.top + __.margin.bottom)
		  		.append("svg:g")
		  		.attr("transform", 
		  				"translate(" + __.margin.left + "," + __.margin.top+ ")");
		  
		 __.dimensions = d3.keys(__.data[0]).filter(function (d) {
			 return d != "cluster";
		 });
		 
		 __.x.domain(__.dimensions);
		 __.x.rangePoints([0, __.width], 1);
		 
		 __.dimensions.forEach(function (attrName) {
			 
			__.data.forEach(function (d) {
				d[attrName] = +d[attrName];
			});
			
			__.y[attrName] = d3.scale.linear()
					.domain(d3.extent(data, function (d) { return d[attrName]; }))
					.range([__.height, 0]);
			__.y[attrName].brush = d3.svg.brush()
					.y(__.y[attrName])
					.on("brush", brush);
		 });
		 
		 __.foreground = container.append("g")
		 		.attr("class", "foreground")
		 		.selectAll("path")
		 		.data(__.data)
		 		.enter().append("path")
		 		.attr("d", path)
		 		.attr("class", function (d) { return d.cluster; })
		 		.style("stroke", function (d) { return __.color(d.cluster); });
		 
		 __.gAttributes = container.selectAll(".attr")
		 		.data(__.dimensions)
		 		.enter().append("g")
		 		.attr("class", "attr")
		 		.attr("transform", function (d) { return "translate("+ __.x(d) +")"; })
		 		.call(d3.behavior.drag()
		 				.origin(function (d) { return {x: __.x(d)}; })
		 				.on("dragstart", dragstart)
		 				.on("drag", drag)
		 				.on("dragend", dragend)
		 				);
		 
		 __.gAttributes.append("g")
		 		.attr("class", "axis")
		 		.each(function (d) {
		 			d3.select(this).call(__.axis.scale(__.y[d]));
		 		}).append("text")
		 		.attr("text-anchor", "middle")
		 		.attr("y", -9)
		 		.text(String);
		 
		 __.gAttributes.append("g")
		 		.attr("class", "brush")
		 		.each(function (d) { d3.select(this).call(__.y[d].brush); })
		 		.selectAll("rect")
		 		.attr("x", -8)
		 		.attr("width", 16);
		 
		 function dragstart(d) {
			 __.dragIndex = __.dimensions.indexOf(d);
		 } // end dragstart()
		 
		 function drag(d) {
			 __.x.range()[__.dragIndex] = d3.event.x;
			 __.dimensions.sort(function(a, b) { return __.x(a) - __.x(b); });
			 __.gAttributes.attr("transform", function (d) {
				return "translate("+ __.x(d)+")" ;
			 });
			 __.foreground.attr("d", __.path);
		 } // end drag()
		 
		 function dragend(d) {
			 __.x.domain(__.dimensions).rangePoints([0, __.width], 1);
			 var t = d3.transition().duration(500);
			 t.selectAll(".attr").attr("transform", function (d) {
				 return "translate(" + __.x(d) +")";
			 });
			 t.selectAll(".foreground path").attr("d", __.path);
		 } // end dragend()
		 
		 function brush() {
			 var actives = __.dimensions.filter(function (p) { return !__.y[p].brush.empty(); }),
			 	extents = actives.map(function (p) { return __.y[p].brush.extent(); });
			 __.foreground.classed("fade", function (d) {
				 return !actives.every(function(p, i) {
					 return extents[i][0] <= d[p] && d[p] <= extents[i][1];
				 });
			 });
		 } // end brush()
		 
		 function path(d) {
			 return __.line(__.dimensions.map(function (attrName) {
				 return [__.x(attrName), __.y[attrName](d[attrName])];
			 }));
		 } // end path()
	
	};  // end parallelcoordinate()
	
	var chart215 = {};
	
	chart215.select = function (selector) {
		return new Chart215(selector);
	};
	
	return chart215;
}());